﻿using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;
using System.Data;
using System.Data.SqlServerCe;
using System.Web;
using System.Net;
using OpenUDIDCSharp;

namespace Apmetrix
{
    class ApmetrixThread : IDisposable
    {
        Thread metrixThread;

        private static readonly string TAG = "Apmetrix SDK / Thread";
        private static readonly string AUTOTEST_TAG = "Thread / AutoTest";
        private static readonly string CONNECTION_TAG = "Thread / Connection";
        private static readonly string SESSION_TAG = "Thread / Session";

        private static readonly int QUEUE_SIZE = 10000;

        // poll timeouts used when buffering data from the queue
        private static readonly int SHORT_POLL_TIMEOUT_MIN = 15000;		// 15 secs (in msec)
        private static readonly int SHORT_POLL_TIMEOUT_MAX = 25000;     //TEMP 25secs //300000;		//  5 mins (in msec)

        // poll timeouts used when uploading data from the queue
        private static readonly int LONG_POLL_TIMEOUT_MIN = 60000; //1Min    // TEMP COMMENT 300000; 		// 5 mins  (in msec)
        private static readonly int LONG_POLL_TIMEOUT_MAX = 300000; //5Mins          // TEMP COMMENT 21600000;		// 6 hrs   (in msec)

        private static readonly int POLL_TIMEOUT_BACKOFF = 2;

        private static int pollTimeout; 								// for both short and long poll

        private static ApmetrixThread singleton = null;

        private static ApmetrixDBInterface dbInterface = null;
        //private static ApmetrixLocationService als = null;
        private static ApmetrixConnectionManager acm = null;

        private static bool dbInitialized = false;
        private static int lastDatasetId = 0;
        private static int sessionId;
        private String externalIPAddress = null;


        // row data stored in sessions table
        private static long lastSessionLastTimestamp;
        private static long initialSessionTimestamp;
        private static long lastRequestTimestamp = 0;

        private int sessionRequestCount = 0;

        private string threadsName = "";

        protected internal BlockingCollection<ApmetrixEvent> queue =
                new BlockingCollection<ApmetrixEvent>(QUEUE_SIZE);

        // valid data flags 
        protected internal static bool validGameDataset = true;
        protected internal static bool validGameUnifier = true;
        protected internal static bool validStartTimer = true;
        protected internal static bool validStopTimer = true;
        protected internal static bool validPauseTimers = true;
        protected internal static bool validResumeTimers = true;
        protected internal static bool invalidTimerTag;

        protected SqlCeDataReader urlReader = null;

        private enum State
        {
            UPLOAD_BUFFERED_EVENTS, UPLOAD_CURRENT_EVENTS, BUFFER_EVENTS
        };

        private enum Connection
        {
            OFFLINE, SERVER_DOWN, ONLINE
        }

        private static string dbgMsg;


        private ApmetrixThread(string threadName)
        {
            threadsName = threadName;
            if (metrixThread == null)
                metrixThread = new Thread(new ThreadStart(PerformWork));


        }


        public static int getSessionID()
        {
            return sessionId;
        }

        public static int getPackets()
        {
            if (dbInterface != null)
            {
                dbInterface.selectCountFromSessionsAndUrls();
                return dbInterface.getPacketCount();
            }
            else
                return 0;
        }

        public static int getSesCount()
        {
            if (dbInterface != null)
            {
                dbInterface.selectCountFromSessionsAndUrls();
                return dbInterface.getSessionCount();
            }
            else
                return 0;

        }



        /*
         * Singleton design pattern for ApmetrixThread
         * getInstance() is called when a session is started
         */
        public static ApmetrixThread getInstance(int datasetId)
        {

            if (ApmetrixConstants.LOG_DEBUG)
                dbgMsg = "*";

            /*
             * initialize database if not initialized or if working with
             * different dataset from last session
             */
            if (!dbInitialized || (lastDatasetId != datasetId))
            {
                initDB(datasetId);
            }
            else
            {
                // update session data
                getNextSessionData();
            }
            // remember datasetId for next session
            lastDatasetId = datasetId;


            // if class has not been instantiated yet
            if (singleton == null)
            {
                singleton = new ApmetrixThread("ApmetrixThread");
            }
            // if thread is not alive
            if (!singleton.metrixThread.IsAlive)
                singleton.metrixThread.Start();

            // return reference to thread
            return singleton;
        }


        /// EasyThread provides a facade to inheriting from a Thread class.
        /// Override the perform work method to perform your tasks.
        /// 
        /// Called when the Thread starts
        /// Note:
        /// This is the Equiv to Run in Java Android SDK....I think
        protected virtual void PerformWork()
        {
            metrixThread.Name = threadsName; //Naming the thread
            metrixThread.IsBackground = true; //Equiv to Java's setDaemon

            //Location Manager Not Implemented Yet ***************************
            // start connection manager
            acm = ApmetrixConnectionManager.getInstance();
            // start location service
            //als = ApmetrixLocationService.getInstance(context);

            // set initial machine state
            State state = State.UPLOAD_BUFFERED_EVENTS;

            while (true)
            {
                switch (state)
                {
                    case State.BUFFER_EVENTS:
                        state = bufferEvents();
                        break;
                    case State.UPLOAD_BUFFERED_EVENTS:
                        state = uploadBufferedEvents();
                        break;
                    case State.UPLOAD_CURRENT_EVENTS:
                        state = uploadCurrentEvents();
                        break;
                }
            }
        }

        /**
        * method to buffer events while device is offline with Application server
        * @return next state to transition to
        */
        private State bufferEvents()
        {
            //Our event
            ApmetrixEvent metrixEvent = null;

            //Temp stores the Event that was pulled out from the Queue
            ApmetrixEvent metrixEventOut = null;

            //TestBuild Not made ***************************
            //if (TEST_BUILD) testMachineState = "buffering events";

            if (ApmetrixConstants.LOG_DEBUG)
                Console.WriteLine("{0} BUFFERING EVENTS", TAG);

            // if database can't be opened switch to state that uploads events as they are queued up
            if (dbInterface.open() != ApmetrixConstants.SQLCE_OK)
                return State.UPLOAD_CURRENT_EVENTS;

            // start at minimum wait time
            pollTimeout = SHORT_POLL_TIMEOUT_MIN;

            while (true)
            {
                try
                {
                    // wait until something available or times out
                    if (ApmetrixConstants.LOG_DEBUG)
                    {
                        Console.WriteLine("{0}  Queue Count: {1}", TAG, queue.Count);
                        Console.WriteLine(" {0} DEBUG: Waiting " + pollTimeout / 1000 +
                                " secs for event to be queued so it can be buffered in database", TAG);
                    }


                    //I think this is the C# equiv to Java's 
                    //queue.poll(pollTimeout, TimeUnit.MILLISECONDS);
                    if (queue.TryTake(out metrixEventOut, pollTimeout))
                        metrixEvent = metrixEventOut;


                }
                catch (ThreadInterruptedException e)
                {
                    Console.WriteLine(" {0} ERROR: interrupted exception polling queue", TAG);
                }
                // if nothing on the queue to poll
                if (metrixEvent == null)
                {
                    // exponentially backoff poll timeout for next time
                    pollTimeout *= POLL_TIMEOUT_BACKOFF;
                    if (pollTimeout >= SHORT_POLL_TIMEOUT_MAX)
                        pollTimeout = SHORT_POLL_TIMEOUT_MAX;

                    // if connected to network switch to state that uploads what has been buffered so far
                    if (getConnectionStatus() == Connection.ONLINE)
                    {
                        dbInterface.close();
                        return State.UPLOAD_BUFFERED_EVENTS;
                    }
                    //else
                    //{
                    //    dbInterface.close();
                    //    return State.BUFFER_EVENTS;
                    //}
                }
                //Commented out from Java Code
                //			// else if intermediate timer event (i.e. one that does not get logged)
                //			else if (intermediateTimerEvent(event)) {
                //				// reset pollTimeout in case it was backed off 
                //				pollTimeout = SHORT_POLL_TIMEOUT_MIN;
                //			}
                // else event retrieved from queue within wait time
                else
                {
                    // reset pollTimeout in case it was backed off 
                    pollTimeout = SHORT_POLL_TIMEOUT_MIN;

                    // create url from event data
                    String url = makeURL(metrixEvent);
                    if (ApmetrixConstants.LOG_DEBUG)
                        Console.WriteLine("{0} Saving to database url: " + url, TAG);
                    if (writeURL2DB(url) != ApmetrixConstants.SQLCE_OK)
                    {
                        Console.WriteLine("Problem writing url to database", TAG);
                        dbInterface.close();
                        return State.UPLOAD_CURRENT_EVENTS;
                    }
                    // if connected to network switch to state that uploads what has been buffered so far
                    if (getConnectionStatus() == Connection.ONLINE)
                    {
                        dbInterface.close();
                        return State.UPLOAD_BUFFERED_EVENTS;
                    }
                    metrixEvent = null;
                    //else
                    //{
                    //    dbInterface.close();
                    //    return State.BUFFER_EVENTS;
                    //}
                }
            }

        }

        /**
         * method to upload buffered events once device is able to make connection
         * with Application server
         * @return next state to transition to
         */
        private State uploadBufferedEvents()
        {
            int sqlId = 0;
            String url = null;

            //if (TEST_BUILD) testMachineState = "uploading buffered events";
            if (ApmetrixConstants.LOG_DEBUG)
                Console.WriteLine("{0} UPLOADING BUFFERED EVENTS", TAG);
            // if device is not online switch to state that buffers the events as they are queued
            if (getConnectionStatus() != Connection.ONLINE)
            {
                return State.BUFFER_EVENTS;
            }
            //// else if can't open database switch to state that uploads events to the server as they are queued
            else if (dbInterface.open() != ApmetrixConstants.SQLCE_OK)
            {
                return State.UPLOAD_CURRENT_EVENTS;
            }
            // else if error getting all row data from urls table switch to state that uploads events to server as they are queued
            else if (dbInterface.selectAllUrls() != ApmetrixConstants.SQLCE_OK)
            {
                dbInterface.close();
                return State.UPLOAD_CURRENT_EVENTS;
            }

            urlReader = dbInterface.getUrlsReader();



            if (ApmetrixConstants.LOG_DEBUG)
            {
                if (urlReader.FieldCount == 0)
                    Console.WriteLine("ERROR: {0} Starting uploadBufferedEvents() but there are 0 packets to upload", TAG);
                else
                    Console.WriteLine("DEBUG: {0} Starting uploadBufferedEvents() with " + urlReader.FieldCount + " packets to upload", TAG);
            }

            while (urlReader.Read())
            {
                try
                {
                    sqlId = dbInterface.getID(urlReader);
                    url = dbInterface.getUrl(urlReader);
                    if (ApmetrixConstants.LOG_DEBUG)
                        Console.WriteLine("DEBUG: {0} trying to upload record: " + sqlId, TAG);
                    sendPacketToServer(url);
                    if (ApmetrixConstants.LOG_DEBUG)
                        Console.WriteLine("DEBUG: {0} trying to delete record: " + sqlId, TAG);
                    deleteDBEntry(sqlId);
                }
                catch (ApmetrixException ae)
                {
                    //Error sending packet to server
                    urlReader.Close();
                    dbInterface.close();
                    //switch state that buffers events as they are queued
                    return State.BUFFER_EVENTS;
                }
            }

            dbInterface.close();
            if (ApmetrixConstants.LOG_DEBUG)
                Console.WriteLine("DEBUG: {0} Upload of buffered data complete", TAG);


            // switch to state that uploads events to the server as they are queued
            return State.UPLOAD_CURRENT_EVENTS;
        }

        /**
         * method to upload current events assuming the device has a connection to the
         * application server and there is no longer any events buffered away
         * @return next state to transition to
         */
        private State uploadCurrentEvents()
        {
            ApmetrixEvent metrixEvent = null;
            ApmetrixEvent metrixEveOut = null;

            //if (TEST_BUILD) testMachineState = "uploading current events"; //No Test Build yet
            if (ApmetrixConstants.LOG_DEBUG)
                Console.WriteLine("{0} DEBUG: starting uploadCurrentEvents", TAG);

            pollTimeout = LONG_POLL_TIMEOUT_MIN;

            while (true)
            {
                try
                {

                    //I think this is the C# equiv to Java's 
                    //queue.poll(pollTimeout, TimeUnit.MILLISECONDS);
                    if (queue.TryTake(out metrixEveOut, pollTimeout))
                    {
                        metrixEvent = metrixEveOut;
                        metrixEveOut = null;
                    }


                }
                catch (ThreadInterruptedException e)
                {
                    Console.WriteLine("ERROR: {0} interrupted exception polling queue", TAG);
                }
                // if nothing on the queue to poll
                if (metrixEvent == null)
                {
                    // exponentially backoff poll timeout for next time
                    pollTimeout *= POLL_TIMEOUT_BACKOFF;
                    if (pollTimeout > LONG_POLL_TIMEOUT_MAX)
                        pollTimeout = LONG_POLL_TIMEOUT_MAX;
                    if (ApmetrixConstants.LOG_DEBUG)
                        Console.WriteLine("DEBUG: {0} Backing off poll timeout to " + pollTimeout / 1000 + " secs", TAG);

                    // if not online switch to state that buffers events as they are queued

                    if (getConnectionStatus() != Connection.ONLINE)
                    {
                        if (ApmetrixConstants.LOG_DEBUG)
                            Console.WriteLine("DEBUG: {0} No longer online, switch to BUFFER_EVENTS", TAG);
                        return State.BUFFER_EVENTS;
                    }
                }

                    //COMMENTED OUT FROM JAVA SDK
                //			// else if intermediate timer event (i.e. one that does not get logged)
                //			else if (intermediateTimerEvent(event)) {
                //				// reset pollTimeout in case it was backed off 
                //				pollTimeout = LONG_POLL_TIMEOUT_MIN;
                //			}
                // else event retrieved from queue within wait time

                else
                {
                    // reset pollTimeout in case it was backed off 
                    pollTimeout = LONG_POLL_TIMEOUT_MIN;


                    // create url from event data
                    String url = makeURL(metrixEvent);

                    //If we ever go offline buffer event asap
                    if (getConnectionStatus() != Connection.ONLINE)
                    {
                        if (ApmetrixConstants.LOG_DEBUG)
                            Console.WriteLine("{0} UploadCurrentEvents DEBUG: Connection gone offline. BUffering Events", TAG);
                        queue.TryAdd(metrixEvent, pollTimeout);
                        return State.BUFFER_EVENTS;
                    }

                    if (ApmetrixConstants.LOG_DEBUG)
                        Console.WriteLine("{0} Uploading current event", TAG);

                    try
                    {
                        sendPacketToServer(url);
                        if (dbInterface.open() == ApmetrixConstants.SQLCE_OK)
                            //Update sessions table
                            dbInterface.updateSessions(lastRequestTimestamp, sessionId);

                        //Delete any old sessions from table no longer needed
                        deleteOldSessionsEntry();

                    }
                    catch (ApmetrixException ae)
                    {
                        //error sending packet to server, try saving in database
                        if (dbInterface.open() == ApmetrixConstants.SQLCE_OK)
                        {
                            writeURL2DB(url);
                            dbInterface.close();
                            //switch state that buffers events as they are queued
                            return State.BUFFER_EVENTS;
                        }
                        else
                        {
                            //Assuming this will put it on the head, BUT I am probs wrong
                            queue.TryAdd(metrixEvent, pollTimeout);

                        }
                    }
                    if (ApmetrixConstants.LOG_DEBUG)
                        Console.WriteLine("DEBUG: {0} Sent directly okay", TAG);

                    metrixEvent = null;
                }
            }
        }




        private static void initDB(int datasetID)
        {

            bool errorEncountered = false;
            string dbName = "ApmetrixLocalDB_" + datasetID + ".sdf";

            Console.WriteLine("DB EXITS " + File.Exists(dbName));


            if (File.Exists(dbName))
                File.Delete(dbName);

            dbInterface = new ApmetrixDBInterface(dbName);

            //if (TEST_BUILD)
            //    testDBFilename = dbName;



            //if database file doesn't exist OR database is not initialized
            if ((!File.Exists(dbName)) || (getNextSessionData() == false))
            {
                try
                {

                    File.Delete(dbName);

                }
                catch (IOException e)
                {
                    Console.WriteLine(e.Message);
                }
                //open or create database based on the database connection
                if (dbInterface.open() == ApmetrixConstants.SQLCE_OK)
                {
                    dbInterface.close();
                }
                else
                {
                    errorEncountered = true;
                }

                //set the initial data
                if (!errorEncountered)
                {
                    lastSessionLastTimestamp = 0;
                    lastRequestTimestamp = 0;
                    initialSessionTimestamp = (long)ApmetrixConstants.GetCurrentMilli();

                    // for auto test purposes
                    //if (TEST_BUILD)
                    //{
                    //    if (testAutoTest)
                    //    {
                    //        // set both lastSessionLastTimestamp and lastRequestTimestamp to same test value
                    //        lastSessionLastTimestamp = testLastSessionLastTimestamp;
                    //        lastRequestTimestamp = testLastSessionLastTimestamp;
                    //        initialSessionTimestamp = testInitialSessionTimestamp;
                    //    }
                    //}

                    sessionId = 1;
                    //now refresh session and save it for the next time...
                    saveSessionData();
                    dbInitialized = true;
                }
            }

        }


        /*
         * get next session data
         * @return the value of dbInitialized
         */

        private static bool getNextSessionData()
        {
            bool errorEncountered = false;

            //open the database for read/write access
            if (dbInterface.open() == ApmetrixConstants.SQLCE_OK)
            {
                //get next session data from database
                if (dbInterface.selectNextSessionData() == ApmetrixConstants.SQLCE_OK)
                {
                    // increment session id from last time
                    sessionId = dbInterface.getLastSessionID() + 1;
                    initialSessionTimestamp = dbInterface.getInitialSessionTS();
                    lastSessionLastTimestamp = dbInterface.getLastSessionLastTS();

                    // for auto test purposes
                    //if (TEST_BUILD)
                    //{
                    //    if (testAutoTest)
                    //    {
                    //        initialSessionTimestamp = testInitialSessionTimestamp;
                    //        // set both lastSessionLastTimestamp and lastRequestTimestamp to same test value
                    //        lastSessionLastTimestamp = testLastSessionLastTimestamp;
                    //        lastRequestTimestamp = testLastSessionLastTimestamp;
                    //    }
                    //}

                }
                else
                {
                    errorEncountered = true;
                }

                //if (TEST_BUILD)
                //{
                //    if (dbInterface.selectCountFromSessionsAndUrls() == SQLITE_OK)
                //    {
                //        testDBSessions = dbInterface.getSessionCount();
                //        testDBPackets = dbInterface.getPacketCount();
                //    }
                //}

                dbInterface.close();
            }
            else
            {
                errorEncountered = true;
            }

            dbInitialized = !errorEncountered;
            if (dbInitialized)
                saveSessionData();

            return dbInitialized;
        }

        private static void saveSessionData()
        {
            //open the databse for read/write access
            if (dbInterface.open() == ApmetrixConstants.SQLCE_OK)
            {
                dbInterface.insertOrReplaceSessions(sessionId, lastSessionLastTimestamp, initialSessionTimestamp);
                dbInterface.close();
            }
        }

        private int writeURL2DB(string url)
        {
            int sqlStatus;

            //open the database for read/write access
            sqlStatus = dbInterface.open();

            if (sqlStatus == ApmetrixConstants.SQLCE_OK)
                //insert the url and session id in urls table
                sqlStatus = dbInterface.insertUrls(url, sessionId);
            if (sqlStatus == ApmetrixConstants.SQLCE_OK)
                //update sessions table
                sqlStatus = dbInterface.updateSessions(lastRequestTimestamp, sessionId);
            if (sqlStatus == ApmetrixConstants.SQLCE_OK)
                //get the urls table data
                sqlStatus = dbInterface.selectAllUrls();


            //if (TEST_BUILD)
            //{
            //    if (dbInterface.selectCountFromSessionsAndUrls() == SQLITE_OK)
            //    {
            //        testDBSessions = dbInterface.getSessionCount();
            //        testDBPackets = dbInterface.getPacketCount();
            //    }
            //    ContextWrapper cw = new ContextWrapper(context);
            //    File dbFile = cw.getDatabasePath(testDBFilename);
            //    testDBFilesize = dbFile.length();
            //}

            if (sqlStatus == ApmetrixConstants.SQLCE_OK)
                //set reader
                urlReader = dbInterface.getUrlsReader();

            //return status of database access

            return sqlStatus;
        }

        /**
         * delete DB entries from urls table based on sqlId and any sessions table entries that are old
         * Note: this routine is called only when machine state == State.UPLOAD_BUFFERED_EVENTS
         * @param sqlId
         */
        private void deleteDBEntry(int sqlId)
        {
            string errorMessage = null;

            if (dbInterface.deleteFromUrls(sqlId) == ApmetrixConstants.SQLCE_OK)
            {
                //if (TEST_BUILD)
                //{
                //    if (dbInterface.selectAllUrls() == SQLITE_OK)
                //        urlsCursor = dbInterface.getUrlsCursor();
                //}

                int highestSession = 0;
                //delete sessions no longer in use

                if (dbInterface.selectHighestSessionFromJoin() == ApmetrixConstants.SQLCE_OK)
                {
                    highestSession = dbInterface.getHighestSession();
                    if (highestSession > 0)
                    {
                        int lowestSession;

                        if (dbInterface.selectLastSessionIdFromSessions() == ApmetrixConstants.SQLCE_OK)
                        {
                            lowestSession = dbInterface.getLowestSession();
                            if (lowestSession < highestSession)
                            {
                                if (dbInterface.deleteLastSessionIdFromSessions(lowestSession) != ApmetrixConstants.SQLCE_OK)
                                    errorMessage = "Error deleting last sessionID from sessions table";
                                //else if (TEST_BUILD)
                                //{
                                //    if (dbInterface.selectCountFromSessionsAndUrls() == ApmetrixConstants.SQLCE_OK)
                                //    {
                                //        testDBSessions = dbInterface.getSessionCount();
                                //        testDBPackets = dbInterface.getPacketCount();
                                //    }

                                //    //SOme file stuff that ill do later
                                //    //ContextWrapper cw = new ContextWrapper(context);
                                //    //File dbFile = cw.getDatabasePath(testDBFilename);
                                //    //testDBFilesize = dbFile.length();
                                //}
                            }
                        }
                        else
                            errorMessage = "Error selecting last sessionID from sessions table";
                    }

                }
                else
                    errorMessage = "Error selecting highest session id";
            }
            else
                errorMessage = "Error deleting row from urls table where 'id' = " + sqlId;

            if (errorMessage != null)
                Console.WriteLine("{0} ERROR: " + errorMessage, TAG);
        }

        /**
         * delete DB entries from sessions table entries that are old
         * Note: this routine is called only when machine state == State.UPLOAD_CURRENT_EVENTS
         */
        private void deleteOldSessionsEntry()
        {
            string errorMessage = null;
            int sessionCount = 0, urlsCount = 0;

            if (dbInterface.selectCountFromSessionsAndUrls() == ApmetrixConstants.SQLCE_OK)
            {
                sessionCount = dbInterface.getSessionCount();
                urlsCount = dbInterface.getPacketCount();
                //only delete old sessions entries if urls table is empty and more than one sessions record
                if (urlsCount == 0 && sessionCount > 1)
                {
                    //get lowest session id
                    if (dbInterface.selectLastSessionIdFromSessions() == ApmetrixConstants.SQLCE_OK)
                    {
                        int lowestSession = dbInterface.getLowestSession();

                        if (dbInterface.deleteLastSessionIdFromSessions(lowestSession) != ApmetrixConstants.SQLCE_OK)
                        {
                            errorMessage = "Error deleting last sessionID from sessions table";
                        }
                        else
                        {
                            --sessionCount;
                            if (ApmetrixConstants.LOG_DEBUG)
                                Console.WriteLine("{0} DEBUG: Deleted unused session id: " + lowestSession, SESSION_TAG);

                        }
                    }
                    else
                        errorMessage = "Error selecting last sessionId from sessions table";
                }
                //if (TEST_BUILD)
                //{
                //    testDBSessions = sessionsCount;
                //    testDBPackets = urlsCount;
                //    ContextWrapper cw = new ContextWrapper(context);
                //    File dbFile = cw.getDatabasePath(testDBFilename);
                //    testDBFilesize = dbFile.length();
                //}
            }
            else
                errorMessage = "Error selecting count from sessions and urls tables";

            if (errorMessage != null)
                Console.WriteLine("{0} ERROR: " + errorMessage, TAG);
        }

        /**
         * since online, add external IP Address and send it all to server
         * @param url String to send to the gateway
         * Figure out how to throw the exception if needed since C# has no throws Exception
         */
        private void sendPacketToServer(String url)
        {
            List<KeyValuePair<string, string>> nameValuePairs = new List<KeyValuePair<string, string>>();

            //if (TEST_BUILD)
            //{
            //    if (testAutoTest)
            //    {
            //        if (testIpAddress != null)
            //            externalIPAddress = testIpAddress;
            //    }
            //}

            //add external IP address to url
            nameValuePairs.Add(new KeyValuePair<string, string>(ApmetrixConstants.Key.GAME_IP_ADDRESS, externalIPAddress));

            // encode name value pairs
            string urlPrefix = System.Web.HttpUtility.UrlEncode(nameValuePairs[0].Key, Encoding.UTF8);
            urlPrefix += "=";
            urlPrefix += System.Web.HttpUtility.UrlEncode(nameValuePairs[0].Value, Encoding.UTF8);

            // add prefix to url
            url = url + "&" + urlPrefix;
            if (ApmetrixConstants.LOG_DEBUG)
                Console.WriteLine("{0} DEBUG: URL: {1}", TAG, url);


            int status = ApmetrixConstants.SQLCE_OK;
            try
            {

                status = ApmetrixServerUtilities.get(url);
            }
            catch (ApmetrixException ae)
            {
                Console.WriteLine("{0} EXCEPTION: {1}", TAG, ae.Message);
                status = ApmetrixConstants.SQLCE_ERROR;
            }

            if (status == ApmetrixServerUtilities.SC_BAD_REQUEST)
                validGameDataset = false;
            else if (status == ApmetrixConstants.HTTP_SC_IM_A_TEAPOT)
                validGameUnifier = false;

            if (ApmetrixConstants.LOG_DEBUG)
            {
                Console.WriteLine("{0} {1}", AUTOTEST_TAG, dbgMsg);
                dbgMsg = "-";
            }


        }

        /**
         * TIMER EVENTS NOT CURRENTLY IMPLEMENTED
         * handle timer events
         * @param event to be processed (if a timer event)
         * @return true if this event is NOT to be logged and is truly an INTERMEDIATE timer event, 
         * otherwise return false so this event can be logged
         */
        private bool intermediateTimerEvent(ApmetrixEvent metrixEvent)
        {
            return true;
        }


        /**
         * Construct url that will be sent to gateway
         * Since this method is called whether online or offline wait until
         * actually sending to gateway to add external IP address.
         * 
         * Note: geo location determined by GPS not Wi-Fi so it can be added here
         * For testing, latitude and longitude for Apmetrix office should be 
         *	lat = 32.916485;
         *	lon = -117.228516;
         *
         * @return url String
         */
        private string makeURL(ApmetrixEvent metrixEvent)
        {
            List<KeyValuePair<string, string>> nameValuePairs = new List<KeyValuePair<string, string>>();
            //geo location determined by how for PC?
            //Hard seting to Apmetrix Office Long/Lat for now

            double lat = 32.916485;
            double lon = -117.228516;

            //if (TEST_BUILD)
            //{
            //    if (testAutoTest)
            //    {
            //        if (testLatitude != LOCATION_UNAVAILABLE)
            //            lat = testLatitude;
            //        if (testLongitude != LOCATION_UNAVAILABLE)
            //            lon = testLongitude;
            //    }
            //}
            if (ApmetrixConstants.LOG_DEBUG)
            {
                Console.WriteLine("Times last session Id = " + sessionId);
                Console.WriteLine("Times initial session time = " + initialSessionTimestamp);
                Console.WriteLine("Times last session last time = " + lastSessionLastTimestamp);
                Console.WriteLine("Times last request time = " + lastRequestTimestamp);
            }

            long currentTime = (long)ApmetrixConstants.GetCurrentMilli();
            int nextSessionRequestCount = sessionRequestCount++;
            nameValuePairs.Add(new KeyValuePair<string, string>(ApmetrixConstants.Key.GAME_TIME, currentTime.ToString()));
            nameValuePairs.Add(new KeyValuePair<string, string>(ApmetrixConstants.Key.GAME_DATASET, metrixEvent.getGamesetId().ToString()));
            nameValuePairs.Add(new KeyValuePair<string, string>(ApmetrixConstants.Key.GAME_UNIFIER, metrixEvent.getGameUnifier().ToString()));
            nameValuePairs.Add(new KeyValuePair<string, string>(ApmetrixConstants.Key.GAME_RETURN, "i"));
            nameValuePairs.Add(new KeyValuePair<string, string>(ApmetrixConstants.Key.GAME_LIBRARY_VERSION, ApmetrixConstants.Value.LIBRARY_VERSION));
            nameValuePairs.Add(new KeyValuePair<string, string>(ApmetrixConstants.Key.VISITOR_ID, metrixEvent.getVisitorId().ToString()));
            nameValuePairs.Add(new KeyValuePair<string, string>(ApmetrixConstants.Key.INITIAL_SESSION_TIME, initialSessionTimestamp.ToString()));
            nameValuePairs.Add(new KeyValuePair<string, string>(ApmetrixConstants.Key.SESSION_REQUEST_COUNT, nextSessionRequestCount.ToString()));
            nameValuePairs.Add(new KeyValuePair<string, string>(ApmetrixConstants.Key.START_SESSION_TIME, metrixEvent.getStartSessionTime().ToString()));

            if (ApmetrixConstants.LOG_DEBUG)
            {
                Console.WriteLine("DEBUG: Times Current Time = {0}", currentTime);
                Console.WriteLine("DEBUG: Times ---------------------------------");
            }

            if (lastSessionLastTimestamp != 0)
                nameValuePairs.Add(new KeyValuePair<string, string>(ApmetrixConstants.Key.LAST_SESSION_LAST_TIME, lastSessionLastTimestamp.ToString()));

            if (lastRequestTimestamp != 0)
                nameValuePairs.Add(new KeyValuePair<string, string>(ApmetrixConstants.Key.LAST_REQUEST_TIME, lastRequestTimestamp.ToString()));

            if (lat != ApmetrixConstants.LOCATION_UNAVAILABLE)
                nameValuePairs.Add(new KeyValuePair<string, string>(ApmetrixConstants.Key.LATITUDE, lat.ToString()));

            if (lon != ApmetrixConstants.LOCATION_UNAVAILABLE)
                nameValuePairs.Add(new KeyValuePair<string, string>(ApmetrixConstants.Key.LONGITUDE, lon.ToString()));

            if (ApmetrixConstants.LOG_DEBUG)
            {
                dbgMsg += (" g.t= " + currentTime.ToString());
                dbgMsg += (", v.fs= " + initialSessionTimestamp.ToString());
                dbgMsg += (", s.s= " + metrixEvent.getStartSessionTime().ToString());
                dbgMsg += (", v.ll= " + lastSessionLastTimestamp.ToString());
                dbgMsg += (", v.cl= " + lastRequestTimestamp.ToString());
            }

            //set last request timestamp for next event
            lastRequestTimestamp = currentTime;

            //for auto test purposes
            //if (TEST_BUILD) {
            //    if (testAutoTest && event.eventType == ApmetrixEvent.END_SESSION_EVENT) {
            //        // save lastRequestTimestamp for next session's lastSessionLastTimestamp
            //        testLastRequestTimestamp = lastRequestTimestamp;
            //    }
            //}

            // get the data for the event
            Dictionary<string, string> apEventData = metrixEvent.getEventData();

            string paramString = ""; //URLEncodedUtils.format(nameValuePairs, "utf-8");

            foreach (KeyValuePair<string, string> eventEntry in apEventData)
            {
                nameValuePairs.Add(new KeyValuePair<string, string>(eventEntry.Key, eventEntry.Value));
            }

            int count = nameValuePairs.Count;
            foreach (KeyValuePair<string, string> keyval in nameValuePairs)
            {
                paramString += keyval.Key + "=";
                paramString += System.Web.HttpUtility.UrlEncode(keyval.Value, Encoding.UTF8);
                if (--count > 0)
                    paramString += "&";
            }

            // string paramString = ""; //URLEncodedUtils.format(nameValuePairs, "utf-8");

            //if (TEST_BUILD)
            //{
            //    return (ApmetrixTest.testGateway + paramString);
            //}

            if (ApmetrixConstants.LOG_DEBUG)
                Console.WriteLine(ApmetrixConstants.DEV_GATEWAY + paramString);

            return (ApmetrixConstants.DEV_GATEWAY + paramString);
        }

        /**
        * check for online connection
        * @return connection status
        */
        private Connection getConnectionStatus()
        {
            //if (TEST_BUILD)
            //{
            //    if (testOnVirtualDevice)
            //    {
            //        if (testOnline)
            //        {
            //            Log.d(CONNECTION_TAG, "Faking online");
            //            return Connection.ONLINE;
            //        }
            //        else
            //        {
            //            Log.d(CONNECTION_TAG, "Faking offline");
            //            return Connection.OFFLINE;
            //        }
            //    }
            //}

            if (!acm.isNetworkConnected())
            {
                if (ApmetrixConstants.LOG_DEBUG)
                    Console.WriteLine("{0} DEBUG: Offline", CONNECTION_TAG);
                return Connection.OFFLINE;
            }
            string address = ApmetrixServerUtilities.getExtIPAddress();

            if (address == null)
            {
                if (ApmetrixConstants.LOG_DEBUG)
                    Console.WriteLine("{0} DEBUG: Server Down", CONNECTION_TAG);
                return Connection.SERVER_DOWN;
            }

            externalIPAddress = address;
            if (ApmetrixConstants.LOG_DEBUG)
                Console.WriteLine("{0} DEBUG: Online", CONNECTION_TAG);
            return Connection.ONLINE;
        }


        public void Pause()
        {
            metrixThread.Suspend();
        }

        public void Quit()
        {
            Cleanup();
        }

        private void Cleanup()
        {
            metrixThread.Join(0);
            metrixThread = null;
        }

        public void Dispose()
        {
            Cleanup();
        }
    }
}
